﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using xplang.fs;
using xplang.abstractions;

namespace xplang.parse
{
  public class SourceReader
  {
    public SourceReader(SourceCode sourceCode)
    {
      this.sourceCode = sourceCode;
      pos = 0;
      start = 0;
      length = sourceCode.length();
    }

    static public readonly String Spaces = " \t\r\n";
    public readonly SourceCode sourceCode;
    public int start;
    public int length;
    public int pos;
    public Token current;

    public void nextToken()
    {
      current = simpleParseToken();
      while (current.type == TokenType.ttComment || current.type == TokenType.ttSpace)
        current = simpleParseToken();
      if (current.type == TokenType.ttIdentifier)
      {
        if (Token.isClause(current.text))
          current.type = TokenType.ttClause;
        else if (Token.isOperator(current.text))
          current.type = TokenType.ttOperator;
        else
        {
          char c = usableCode();
          if (c == '(')
          {
            current.type = TokenType.ttFunction;
          }
          //else
          //while (c == '.')
          //{
          //  pos++;
          //  c = usableCode();
          //  if ((c == '_') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
          //  {
          //    pos++;
          //    Token t2 = parseIdentifier(c);
          //    current.text += "." + t2.text;
          //    current.length = pos - current.start;
          //    current.type = TokenType.ttQualifiedIdentifier;
          //  }
          //  else
          //  {
          //    current=new Token(sourceCode, current.start, pos - current.start, -1, TokenType.ttError, null);
          //    return;
          //  }
          //  c = usableCode();
          //  if (c == '(')
          //    current.type = TokenType.ttFunction;
          //}
        }
      }
    }

    public char usableCode()
    {
      while (true)
      {
        char c = sourceCode.charAt(pos);
        if (Spaces.Contains(c))
        {
          pos++;
          while (Spaces.Contains(sourceCode.charAt(pos)))
            pos++;
        }
        else
        {
          String word = String.Concat(c, sourceCode.charAt(pos + 1));
          if (word == "//")
            parseLineComment(2);
          else if (word == "/*")
            parseBlockComment(2);
          else
            return c;
        }
      }
    }

    public Token simpleParseToken()
    {
      char c = sourceCode.charAt(pos);
      pos++;
      String complex = String.Concat(c, sourceCode.charAt(pos));
      if (Spaces.Contains(c))
        return parseSpaces();
      if (c == '\'')
        return parseString(TokenType.ttStringSingle, c);
      if (c == '\"')
        return parseString(TokenType.ttStringDouble, c);
      if (c >= '0' && c <= '9')
        return parseNumber(c);
      if ((c == '_') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
        return parseIdentifier(c);

      if (complex == "//")
        return parseLineComment(1);
      if (complex == "/*")
        return parseBlockComment(1);

      switch (complex)
      {
        case "||":
          return parseOperator(c, 2, 32);
        case "&&":
          return parseOperator(c, 2, 31);
        case ">=":
          return parseOperator(c, 2, 20);
        case "<=":
          return parseOperator(c, 2, 20);
        case "!=":
          return parseOperator(c, 2, 20);
        case "++":
          return parseOperator(c, 2, Token.PreOrPostOperator);
        case "--":
          return parseOperator(c, 2, Token.PreOrPostOperator);
        case "+=":
          return parseOperator(c, 2, Token.PostOperator);
        case "-=":
          return parseOperator(c, 2, Token.PostOperator);
        case "*=":
          return parseOperator(c, 2, Token.PostOperator);
        case "/=":
          return parseOperator(c, 2, Token.PostOperator);
      }

      switch (c)
      {
        case '.':
          return parseOperator(c, Token.PostOperator);
        case ';':
          return parseOperator(c, -1);
        case ',':
          return parseOperator(c, -1);
        case '{':
          return parseOperator(c, -1);
        case '}':
          return parseOperator(c, -1);
        case '(':
          return parseOperator(c, -1);
        case ')':
          return parseOperator(c, -1);
        case '?':
          return parseOperator(c, Token.PostOperator);
        case ':':
          return parseOperator(c, -1);
        case '+':
          return parseOperator(c, 10);
        case '-':
          return parseOperator(c, 10);
        case '*':
          return parseOperator(c, 9);
        case '/':
          return parseOperator(c, 9);
        case '<':
          return parseOperator(c, 20);
        case '>':
          return parseOperator(c, 20);
        case '=':
          return parseOperator(c, Token.PostOperator);
        case '!':
          return parseOperator(c, Token.PreOperator);
      }

      if (c == char.MaxValue)
        return new Token(sourceCode, pos - 1, 0, -1, TokenType.ttEOF, null);
      return new Token(sourceCode, pos - 1, 1, -1, TokenType.ttError, c + "");

    }

    public Token parseOperator(char c, int prec)
    {
      return new Token(sourceCode, pos - 1, 1, prec, TokenType.ttOperator, c + "");
    }

    public Token parseOperator(char c, int count, int prec)
    {
      StringBuilder s = new StringBuilder();
      s.Append(c);
      for (int i = 1; i < count; i++)
      {
        s.Append(sourceCode.charAt(pos));
        pos++;
      }
      return new Token(sourceCode, pos - count, count, prec, TokenType.ttOperator, s.ToString());
    }

    public Token parseSpaces()
    {
      int i = pos - 1;
      char c = sourceCode.charAt(pos);
      while (Spaces.Contains(c))
      {
        pos++;
        c = sourceCode.charAt(pos);
      }
      return new Token(sourceCode, i, pos - i, -1, TokenType.ttSpace, null);
    }

    public Token parseString(TokenType type, char stop)
    {
      int i = pos - 1;
      char c = sourceCode.charAt(pos);
      pos++;
      StringBuilder s = new StringBuilder();
      while (c != stop && c != char.MaxValue)
      {
        if (c == '\\')
        {
          char c2 = sourceCode.charAt(pos);
          pos++;
          switch (c2)
          {
            case 't':
              s.Append('\t');
              break;
            case 'n':
              s.Append('\n');
              break;
            case 'r':
              s.Append('\r');
              break;
            case '\\':
              s.Append('\\');
              break;
            case '\'':
              s.Append('\'');
              break;
            case '\"':
              s.Append('\"');
              break;
            default:
              s.Append(c);
              break;
          }
        }
        else
          s.Append(c);
        c = sourceCode.charAt(pos);
        pos++;
      }
      return new Token(sourceCode, i, pos - i, -1, type, s.ToString());
    }

    public Token parseIdentifier(char c)
    {
      int i = pos - 1;
      StringBuilder s = new StringBuilder();
      s.Append(c);
      c = sourceCode.charAt(pos);
      while ((c == '_') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
      {
        s.Append(c);
        pos++;
        c = sourceCode.charAt(pos);
      }
      return new Token(sourceCode, i, pos - i, -1, TokenType.ttIdentifier, s.ToString());
    }

    public Token parseNumber(char c)
    {
      int i = pos - 1;
      bool dec = false;
      StringBuilder s = new StringBuilder();
      s.Append(c);
      c = sourceCode.charAt(pos);
      while ((c == '.') || (c >= '0' && c <= '9'))
      {
        if (c == '.')
        {
          if (dec)
            break;
          dec = true;
        }
        s.Append(c);
        pos++;
        c = sourceCode.charAt(pos);
      }
      return new Token(sourceCode, i, pos - i, -1, dec ? TokenType.ttDecNumber : TokenType.ttIntNumber, s.ToString());
    }

    public Token parseLineComment(int skip)
    {
      int i = pos - 1;
      pos += skip;
      char c = sourceCode.charAt(pos);
      while (c != '\r' && c != '\n' && c != char.MaxValue)
      {
        pos++;
        c = sourceCode.charAt(pos);
      }
      while (c == '\r' || c == '\n')
      {
        pos++;
        c = sourceCode.charAt(pos);
      }
      return new Token(sourceCode, i, pos - i, -1, TokenType.ttComment, null);
    }

    public Token parseBlockComment(int skip)
    {
      int i = pos - 1;
      pos += skip;
      char c1 = sourceCode.charAt(pos);
      char c2 = sourceCode.charAt(pos + 1);
      pos += 2;
      if (c1 != char.MaxValue)
        while (c1 != '*' && c2 != '/' && c2 != 0)
        {
          c1 = c2;
          c2 = sourceCode.charAt(pos);
          pos++;
        }
      return new Token(sourceCode, i, pos - i, -1, TokenType.ttComment, null);
    }
  }
}
